import unittest
import sys
from piece import Piece
from const import _Const
from reader import Reader

CONST = _Const()

class Board:

    def __init__(self):
        self.err_words = {}

    #could return list of errorous words
    #by refactor out method for getting words
    def validate_line(self, dic, idx, arr, flipped):
        """validates a single line and saves errorous word in a dictionary"""
        result = []
        word = ""
        hor = 0
        for char in arr:
           if char == '.':
                if dic.has_key(word):
                    for vchar in word:
                        result.append(CONST.VALID_WORD)
                        hor += 1
                else:
                    if len(word) == 1:
                        result.append(CONST.SINGLE_LETTER)
                        hor += 1
                    else:
                        for vchar in word:
                            result.append(CONST.ERROROUS_WORD)
                            if flipped:
                                self.err_words[str(hor) + "-" + str(idx)] = word
                            else:
                                self.err_words[str(idx) + "-" + str(hor)] = word
                            hor += 1
                hor += 1
                word = ""
                result.append(CONST.EMPTY_SPACE)
           else:
                word += char
            
        
        if dic.has_key(word):
            for vchar in word:
                result.append(CONST.VALID_WORD)
        else:
            if len(word) == 1:
                result.append(CONST.SINGLE_LETTER)
            else:
                for vchar in word:
                    result.append(CONST.ERROROUS_WORD)
        
        return result

    def flip_the_board(self, board):
        """flippes the board so vertical line becomes horizontal"""
        result = []
        rotated = list(zip(*board)[::-1])
        for myz in rotated:
            result.append(list(myz))

        rotated = zip(result[::-1])
        return [item for sublist in rotated for item in sublist] # flatten the list

    def validate_horizontal_lines(self, dic, board, flipped):
        """validate all the horizontal lines of a board
           but we must indicate wheter the board is flipped or not"""
        result = []
        ver = 0
        for row in board:
            result.append(self.validate_line(dic, ver, row, flipped))
            ver += 1 
        return result
            
    def merge_boards(self, board, flipped):
        """merges a board with validated horizontal lines with a board 
           that was flipped and also validated.
           The result is a single board with their validation merged"""
        result = board
        hor = 0
        ver = 0
        for row in flipped:
            for val in row:
                if val == CONST.VALID_WORD:
                    result[ver][hor] = CONST.VALID_WORD
                if val == CONST.ERROROUS_WORD:
                    result[ver][hor] = CONST.ERROROUS_WORD
                hor += 1
            hor = 0
            ver += 1
        return result

    def get_scrabble_truth_board(self, dic, board):
        """gets a truth board for the scrabble by validating all 
           horizontal lines, and then flippes the board and does the 
           validation again.  Ends by flipping the flipped board back
           and then merging the boards"""
        t_board = self.validate_horizontal_lines(dic, board, False);
        flip_board = self.flip_the_board(board)
        
        tflip_board = self.validate_horizontal_lines(dic, flip_board, True);
        tflip_back = self.flip_the_board(tflip_board)
        result = self.merge_boards(t_board, tflip_back)

        return result

    def play_piece(self, board, piece):
        """plays one piece and puts in on the 
           board at the correct scrabble position"""
        board[piece.x][piece.y] = piece.letter
        return board

    def play_moves(self, board, moves):
        """play multiple pieces on an scrabble board"""
        for move in moves:
            board = self.play_piece(board, move)
        return board

    def check_if_board_is_valid(self, dic, board):
        """returns true if no position on the board has an 
           errorous word"""
        truth_board = self.get_scrabble_truth_board(dic, board)
        for row in truth_board:
            for val in row:
                if val != CONST.VALID_WORD and val != CONST.EMPTY_SPACE:
                    return False

        return True

    def is_bad_start(self, dic, board):
        """returns BAD START: word if an errorous word is found"""
        t_board = self.get_scrabble_truth_board(dic, board);
        ver = 0
        for row in t_board:
            hor = 0
            for char in row:
                if char == CONST.ERROROUS_WORD:
                    return self.err_words[str(ver) + "-" + str(hor)]
                hor += 1
            ver += 1
        return ""

    def pretty_print(self, board):
        """prints the board one line at a time"""
        for row in board:
            print row

    def get_sequence_order(self, moves):
        """checks if all the pices in either the horizontal line or 
           vertical line are in the same row"""

        if len(moves) <= 1:  #single piece is always in seq
            return CONST.HORIZONTAL

        result = True
        cmpx = moves[0].x  #here the moves are > 1
        for move in moves:
            if cmpx != move.x:
                result = False
                break
        
        if result:
            return CONST.HORIZONTAL

        result = True
        cmpy = moves[0].y
        for move in moves:
            if cmpy != move.y:
                result = False
                break

        if result:
            return CONST.VERTICAL
        else:
            return CONST.INVALID_SEQUENCE

    def sort(self, seq, moves):
        if seq == CONST.HORIZONTAL:
            moves = sorted(moves, key=lambda piece: piece.y)
        elif seq == CONST.VERTICAL:
            moves = sorted(moves, key=lambda piece: piece.x)
        return moves

    def get_row(self, seq, moves):
        if len(moves) == 0:
            return -1
        if seq == CONST.HORIZONTAL:
            return moves[0].x
        else:
            return moves[0].y

    def get_range(self, seq, moves):
        """get the range from the smallest to the largest move
           e.g. 2 moves B1 and E1 will return range(1, 4) 
           which mean that range 2 and 3 could be blank"""
        if len(moves) == 0:
            return range(0,0)
        if seq == CONST.HORIZONTAL:
            return range(moves[0].y, moves[-1].y+1)
        else:
            return range(moves[0].x, moves[-1].x+1)

    def get_possible_moves(self, board, moves):
        seq = self.get_sequence_order(moves)
        moves = self.sort(seq, moves)
        row = self.get_row(seq, moves)
        possible_moves = []
        for i in self.get_range(seq, moves):
            p = Piece()
            if seq == CONST.HORIZONTAL:
                p.x = row
                p.y = i
                p.letter = board[row][i]
            elif seq == CONST.VERTICAL:
                p.x = i
                p.y = row
                p.letter = board[i][row]
            possible_moves.append(p)
        return possible_moves

    def is_no_empty_squares(self, board, moves):
        """check if there are empty squares after playing the pieces on the board"""

        possible_moves = self.get_possible_moves(board, moves)

        #clear all playing pieces from the sequnce
        for p_move in possible_moves[:]:
            for move in moves:
                if p_move == move:
                    possible_moves.remove(p_move)

        #check if the sequence has any empty spaces
        for move in possible_moves:
            if move.letter == '.':
                return False
        return True

    def is_no_overwrite(self, board, moves):
        """checks if any pieces overwrite a letter on the board,
           meaning that all moves are on an empty square"""

        possible_moves = self.get_possible_moves(board, moves)

        for p_move in possible_moves[:]:
            for move in moves:
                if p_move == move:
                    if p_move.letter != '.':
                        return False
        return True
    
    def get_possible_adjents(self, piece):
        """get adjents for the piece and strip off alll
           out of bound indexes"""
        result = []
        result.append((piece.x-1, piece.y))
        result.append((piece.x+1, piece.y))
        result.append((piece.x, piece.y-1))
        result.append((piece.x, piece.y+1))
        for tu in result[:]:
            if tu[0] < 0 or tu[1] < 0:
                result.remove(tu)
            if tu[0] > 14 or tu[1] > 14:
                result.remove(tu)
        return result

    def is_adjent(self, board, moves):
        """checks if any pieces overwrite a letter on the board,
           meaning that all moves are on an empty square"""

        #possible_moves = self.get_possible_moves(board, moves)
        for move in moves:
            adjents = self.get_possible_adjents(move)
            for tu in adjents:
                if board[tu[0]][tu[1]] != '.':
                    return True
            
        return False
        

#TODO:
#  move errword code to function
#  return a list of err index (not truth board)
#  rename validate_horizontal_lines to e.g. validate_horizontal_board
#  add BAD START:  to is_bad_start
#  should i change array to dict using letters and words instead of number array
class Scrabble:
    
    def __init__(self):
        self.myboard = Board()
        self.board = []
        self.moves = []
        self.dict = {}

    def read_files(self, board_file, move_file):
        try:
            self.board = Reader.read_board(board_file)
            moves = Reader.read_move(move_file)
            for move in moves:
                self.moves.append(Piece.parse(move))
        except Exception, e:
            print "Error reading scrabble files:"
            print "->" + str(e)

        try:
            self.dict = Reader.read_dict("SOWPODS.txt")
        except:
            print "Dictionary SOWPODS.txt not found"

    def check_bad_start(self):
        word = self.myboard.is_bad_start(self.dict, self.board)
        if word == "":
            return (False, "")
        else:
            return (True, "BAD START: " + word)

    def check_bad_move(self):
        seq = self.myboard.get_sequence_order(self.moves)
        if seq == CONST.INVALID_SEQUENCE:
            return (True, "BAD MOVE")

        if not self.myboard.is_no_empty_squares(self.board, self.moves):
            return (True, "BAD MOVE")

        if not self.myboard.is_no_overwrite(self.board, self.moves):
            return (True, "BAD MOVE")

        if not self.myboard.is_adjent(self.board, self.moves):
            return (True, "BAD MOVE")

        return (False, "")

    def check_is_not_valid(self):
        played_board = self.myboard.play_moves(self.board, self.moves)

        word = self.myboard.is_bad_start(self.dict, played_board)
        if word == "":
            return (False, "")
        else:
            return (True, "BAD WORD: " + word)
        

    def print_current_board(self):
        self.myboard.pretty_print(self.board)


def main():
    if len(sys.argv) != 3:
        print "usage: python scrabble [boardfile] [movefile]"
        return
    
    scrabble = Scrabble()
    scrabble.read_files(sys.argv[1],sys.argv[2])

#    scrabble.print_current_board()
    
    #check Bad start
    res = scrabble.check_bad_start()
    if res[0]:
        print res[1]
        return 

    #check Bad move
    res = scrabble.check_bad_move()
    if res[0]:
        print res[1]
        return

    #check Bad word
    res = scrabble.check_is_not_valid()
    if res[0]:
        print res[1]
        return 

    print "VALID"

    
if __name__ == "__main__":
    main()
